﻿using System;
using System.Diagnostics;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Xml.Linq;
using log4net;

namespace Klinkby.MicroBus
{
    [Serializable]
    internal abstract class XSerializable<T> : MarshalByRefObject
    {
        const string _namespace = "urn:schemas-kli-dk:microbus-configuration";
        readonly static ILog _log = LogManager.GetLogger(typeof(XSerializable<T>));

        [DebuggerStepThrough]
        protected XSerializable()
        {
            // ILB
        }

        internal delegate T Factory(XElement e, T[] children);

        /// <summary>
        /// Builds a DOM tree of T from the a hierachial xml structure.
        /// The element names correspond to the types in this namespace.
        /// Invokes a method of type <see cref="Factory"/> called FromXml on each type to allow
        /// each type to do its own deserialization.
        /// </summary>
        /// <param name="element">source</param>
        /// <param name="addChild">Method that should add child (second parameter) to the parent (first parameter)</param>
        /// <returns></returns>
        public static T FromXml(XElement element, Type siblingType)
        {
            _log.Debug("Deserializing " + element.Name.LocalName);
            if (element.Name.NamespaceName != _namespace)
                throw new ArgumentException("Invalid namespace " + element.Name.NamespaceName);
            var headObj = CreateFromName(
                element, 
                (from childElement in element.Elements()
                select FromXml(childElement, siblingType)).ToArray(),
                siblingType);
            return headObj;
        }

        static T CreateFromName(XElement e, T[] children, Type siblingType)
        {
            var type = GetLocalType(e, siblingType);
            var fromXml = (Factory)Delegate.CreateDelegate(typeof(Factory), type, "FromXml");
            return fromXml(e, children);
        }

        static Type GetLocalType(XElement e, Type siblingType)
        {
            Debug.Assert(e != null);
            string typeName = siblingType.Namespace + "." + e.Name.LocalName;
            return siblingType.Assembly.GetType(typeName);
        }
 
    }
}
